﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.Serialization.Formatters.Binary;
using System.Collections.ObjectModel;
using System.Threading.Tasks;

namespace Skywave.ComponentModel
{
    public class ProfileManager
    {
        static System.Runtime.Serialization.DataContractSerializer serializer_Cache = new System.Runtime.Serialization.DataContractSerializer(typeof(ProfileManagerCache));
        static BinaryFormatter serializer_DefaultStream = new BinaryFormatter();
        //
        public event EventHandler<ProfileManagerRemoteProfileGetListEventArgs> RemoteProfileGetList;
        public event EventHandler<ProfileManagerRemoteProfileDownloadEventArgs> RemoteProfileDownload;
        public event EventHandler<ProfileManagerRemoteProfileUploadEventArgs> RemoteProfileUpload;
        //
        string path_Cache = "Cache.data";
        string path_Profiles_Storage = "Storage\\";
        string path_Base = "";
        System.IO.DirectoryInfo dir_Profiles_Storage;
        System.IO.FileInfo file_Cache;
        //
        ProfileManagerCache _Cache;
        List<Profile> profiles_Remote_Retreived;

        public ProfileManager(string storagePath)
        {
            path_Base = storagePath;
        }

        public void InitializeMe()
        {
            if (!path_Base.EndsWith("\\"))
                path_Base += "\\";
            if (!System.IO.Directory.Exists(path_Base))
                System.IO.Directory.CreateDirectory(path_Base);
            //
            dir_Profiles_Storage = new System.IO.DirectoryInfo(path_Base + path_Profiles_Storage);
            if (!dir_Profiles_Storage.Exists)
                dir_Profiles_Storage.Create();
            file_Cache = new System.IO.FileInfo(path_Base + path_Cache);
            //
            if (file_Cache.Exists)
            {
                using (System.IO.FileStream fs1 = new System.IO.FileStream(file_Cache.FullName, System.IO.FileMode.Open, System.IO.FileAccess.Read))
                {
                    _Cache = serializer_Cache.ReadObject(fs1) as ProfileManagerCache;
                }
            }
            if (_Cache == null)
                _Cache = new ProfileManagerCache();
            //
            RemoteProfile_GetList();
            //
            if (System.IO.File.Exists(path_Base + "Names.bin"))
            {
                _ImportOld();
            }
            //
            Cache_Save();
        }

        private int GetFreeId()
        {
            return (_Cache.Profiles.Count == 0) ? 1 : (from x1 in _Cache.Profiles
                                                       select x1.Id).Max() + 1;
        }

        private void _ImportOld()
        {
            DateTime nowUTC = DateTime.UtcNow;
            ProfilesHolder ph1 = new ProfilesHolder(path_Base);
            foreach (string fe1 in ph1.GetProfileNames())
            {
                Profile p1 = new Profile() { Id = -1, Text = fe1, LastSaveDate = nowUTC };
                System.IO.MemoryStream ms1 = ph1.Load(fe1);
                //
                Profile_AddOrSave(p1, ms1, false);
            }
            //
            System.IO.DirectoryInfo dirOldBackup = new System.IO.DirectoryInfo(path_Base + "_OldBackup");
            if (!dirOldBackup.Exists)
                dirOldBackup.Create();
            //
            foreach (System.IO.FileInfo fe1 in (new System.IO.DirectoryInfo(path_Base)).GetFiles())
            {
                if (fe1.Name == "Names.bin" || fe1.Name.StartsWith("Profile_"))
                {
                    fe1.MoveTo(dirOldBackup.FullName + "\\" + fe1.Name);
                }
            }
        }

        public System.IO.FileInfo GetDefaultFile(Profile p1)
        {
            return new System.IO.FileInfo(GetStorageDirectory(p1).FullName + "Default.data");
        }

        public System.IO.MemoryStream GetDefaultStream(Profile p1)
        {
            System.IO.MemoryStream r = null;
            //
            System.IO.FileInfo fileDefault = GetDefaultFile(p1);
            if (fileDefault.Exists)
            {
                using (System.IO.FileStream fs1 = new System.IO.FileStream(fileDefault.FullName, System.IO.FileMode.Open, System.IO.FileAccess.Read))
                {
                    r = serializer_DefaultStream.Deserialize(fs1) as System.IO.MemoryStream;
                    if (r != null)
                        r.Position = 0;
                }
            }
            //
            return r;
        }
        public System.IO.DirectoryInfo GetStorageDirectory(Profile p1)
        {
            System.IO.DirectoryInfo r = new System.IO.DirectoryInfo(dir_Profiles_Storage.FullName + "\\" + p1.Id.ToString() + "\\");
            if (!r.Exists)
                r.Create();
            return r;
        }
        public void Profile_AddOrSave(Profile p1, System.IO.MemoryStream defaultStream, bool overwriteSameText = true)
        {
            Profile pInCache = _Cache.Profiles.SingleOrDefault(q => q.Id == p1.Id);
            //
            if (pInCache == null)
            {
                if (!overwriteSameText)
                {
                    p1.Text = _FindFreeText(p1.Text);
                    //
                    pInCache = p1;
                    pInCache.Id = GetFreeId();
                }
                else
                {
                    pInCache = _Cache.Profiles.SingleOrDefault(q => q.Text == p1.Text);
                    if (pInCache == null)
                    {
                        pInCache = p1;
                        pInCache.Id = GetFreeId();
                    }
                }
                _Cache.Profiles.Add(pInCache);
            }
            //
            pInCache.LastSaveDate = DateTime.UtcNow;
            //
            if (defaultStream != null)
            {
                System.IO.FileInfo fileDefault = GetDefaultFile(pInCache);
                defaultStream.Position = 0;
                using (System.IO.FileStream fs1 = new System.IO.FileStream(fileDefault.FullName, System.IO.FileMode.Create))
                {
                    serializer_DefaultStream.Serialize(fs1, defaultStream);
                }
            }
            //
            Cache_Save();
        }

        public void Cache_Save()
        {
            using (System.IO.FileStream fs1 = new System.IO.FileStream(file_Cache.FullName, System.IO.FileMode.Create))
            {
                serializer_Cache.WriteObject(fs1, _Cache);
            }
        }

        public void RemoteProfile_GetList()
        {
            if (RemoteProfileGetList != null)
            {
                ProfileManagerRemoteProfileGetListEventArgs args1 = new ProfileManagerRemoteProfileGetListEventArgs();
                RemoteProfileGetList(this, args1);
                if (args1.Profiles != null)
                    profiles_Remote_Retreived = args1.Profiles;
                else
                    profiles_Remote_Retreived = new List<Profile>();
            }
            else
                profiles_Remote_Retreived = new List<Profile>();
            //
            foreach (var fe1 in from x1 in profiles_Remote_Retreived
                                join j2 in _Cache.Profiles on x1.RemoteId equals j2.RemoteId into gj2
                                from x2 in gj2.DefaultIfEmpty()
                                select new
                                {
                                    Profile = x1,
                                    ProfileInCache = x2
                                })
            {
                if (fe1.ProfileInCache == null)
                {
                    fe1.Profile.Remote_LastUpdateDateInCache = null;
                    fe1.Profile.Id = GetFreeId();
                    //
                    Profile profileInCache_SameText = _Cache.Profiles.SingleOrDefault(q => q.Text == fe1.Profile.Text);
                    //
                    _Cache.Profiles.Add(fe1.Profile);
                    //
                    if (profileInCache_SameText != null)
                        profileInCache_SameText.Text = _FindFreeText(profileInCache_SameText.Text);
                }
                else
                {
                    fe1.ProfileInCache.Remote_LastUpdateDate = fe1.Profile.Remote_LastUpdateDate;
                }
            }
            //
            foreach (var fe1 in from x1 in _Cache.Profiles.Where(q => q.RemoteId != null)
                                join j1 in profiles_Remote_Retreived on x1.RemoteId equals j1.RemoteId into gj1
                                from x2 in gj1.DefaultIfEmpty()
                                select new
                                {
                                    Profile = x1,
                                    ProfileRetrieved = x2
                                })
            {
                fe1.Profile.IsRemote = (fe1.ProfileRetrieved != null);
            }
        }

        public async Task<bool> RemoteProfile_Download(int remoteProfileId, bool forceDownload = false)
        {
            System.IO.DirectoryInfo r = null;
            if (RemoteProfileDownload != null)
            {
                Profile profile_Selected = _Cache.Profiles.SingleOrDefault(q => q.RemoteId == remoteProfileId);
                if (profile_Selected != null)
                {
                    if (!forceDownload && profile_Selected.Remote_LastUpdateDateInCache == profile_Selected.Remote_LastUpdateDate)
                    {
                        r = GetStorageDirectory(profile_Selected);
                        if (!r.Exists)
                            r = null;
                    }
                    //
                    if (r == null)
                    {
                        ProfileManagerRemoteProfileDownloadEventArgs args1 = new ProfileManagerRemoteProfileDownloadEventArgs();
                        args1.Profile = profile_Selected;
                        RemoteProfileDownload(this, args1);
                        //
                        if (!args1.IsCancel)
                        {
                            IO.Compression.ZipStorer zip1 = null;
                            if (args1.ProfileContentZiped != null)
                                zip1 = IO.Compression.ZipStorerFile.Open(args1.ProfileContentZiped, System.IO.FileAccess.Read);
                            else if (!string.IsNullOrEmpty(args1.ProfilePathZiped) && System.IO.File.Exists(args1.ProfilePathZiped))
                                zip1 = await IO.Compression.ZipStorerFile.Open(args1.ProfilePathZiped, System.IO.FileAccess.Read);
                            //
                            if (zip1 != null)
                            {
                                r = GetStorageDirectory(profile_Selected);
                                if (!r.Exists)
                                {
                                    r.Create();
                                    r.Refresh();
                                }
                                //
                                string path;
                                List<IO.Compression.ZipStorer.ZipFileEntry> dir = zip1.ReadCentralDir();
                                foreach (IO.Compression.ZipStorer.ZipFileEntry entry in dir)
                                {
                                    path = System.IO.Path.Combine(r.FullName, entry.FilenameInZip);
                                    await zip1.ExtractFile(entry, path);
                                }
                                //
                                zip1.Close();
                            }
                            //
                            profile_Selected.Remote_LastUpdateDateInCache = profile_Selected.Remote_LastUpdateDate;
                            profile_Selected.LastSaveDate = DateTime.UtcNow;
                            Cache_Save();
                        }
                    }
                }
            }
            //
            return r != null;
        }

        public async Task<bool> RemoteProfile_Upload(int profileId)
        {
            bool r = false;
            //
            if (RemoteProfileUpload != null)
            {
                Profile profile_Selected = _Cache.Profiles.SingleOrDefault(q => q.Id == profileId);
                if (profile_Selected != null)
                {
                    System.IO.DirectoryInfo dirStorage = GetStorageDirectory(profile_Selected);
                    if (dirStorage.Exists)
                    {
                        string filePath_Temp = System.IO.Path.GetTempFileName();
                        using (IO.Compression.ZipStorer zip1 = await IO.Compression.ZipStorerFile.Create(filePath_Temp, "Remote Profile"))
                        {
                            await zip1.AddFolder(IO.Compression.ZipStorer.Compression.Deflate, GetStorageDirectory(profile_Selected).FullName, false);
                        }
                        //
                        ProfileManagerRemoteProfileUploadEventArgs args1 = new ProfileManagerRemoteProfileUploadEventArgs();
                        args1.Profile = profile_Selected;
                        args1.ContentPathZiped = filePath_Temp;
                        //
                        RemoteProfileUpload(this, args1);
                        //
                        if (!args1.IsCancel)
                        {
                            profile_Selected.Remote_LastUpdateDateInCache = profile_Selected.Remote_LastUpdateDate;
                            Cache_Save();
                            r = true;
                        }
                    }
                }
            }
            //
            return r;
        }

        /// <summary>
        /// ...
        /// </summary>
        public ObservableCollection<Profile> Profiles
        {
            get { return (_Cache == null) ? null : _Cache.Profiles; }
            set { if (_Cache != null) _Cache.Profiles = value; }
        }

        /// <summary>
        /// ...
        /// </summary>
        public int? DefaultProfileId
        {
            get { return (_Cache == null) ? null : _Cache.DefaultProfileId; }
            set { if (_Cache != null) _Cache.DefaultProfileId = value; }
        }


        public void Profile_Delete(Profile p1)
        {
            System.IO.DirectoryInfo dirStorage = GetStorageDirectory(p1);
            if (dirStorage.Exists)
                dirStorage.Delete(true);
            //
            _Cache.Profiles.Remove(p1);
            if (_Cache.DefaultProfileId == p1.Id)
                _Cache.DefaultProfileId = -1;
            //
            Cache_Save();
        }

        public void Profile_Clone(Profile p1)
        {
            Profile pClone = new Profile();
            pClone.LastSaveDate = p1.LastSaveDate;
            pClone.Text = _FindFreeText(p1.Text);
            pClone.Id = GetFreeId();
            //
            System.IO.DirectoryInfo dirSource = GetStorageDirectory(p1);
            System.IO.DirectoryInfo dirDest = GetStorageDirectory(pClone);
            //
            IO.PathHelper.Copy(dirSource.FullName, dirDest.FullName, false, IO.PathHelper.CopyMode.Overwrite);
            //
            _Cache.Profiles.Add(pClone);
            //
            Cache_Save();
        }

        private string _FindFreeText(string startText)
        {
            int i1 = 0;
            string s1 = startText;
            while (_Cache.Profiles.Where(q => q.Text == s1).Count() > 0)
            {
                i1++;
                s1 = string.Format("{0}_{1}", startText, i1);
            }
            //
            return s1;
        }
    }
}
